"""
实时更新系统

实现了模型的实时更新和适应功能
"""

import numpy as np
from typing import Dict, List, Optional, Tuple
from loguru import logger
from sklearn.ensemble import IsolationForest
from sklearn.neighbors import LocalOutlierFactor


class RealTimeUpdateSystem:
    """实时更新系统"""
    
    def __init__(self, 
                 max_samples_per_key: int = 100,
                 update_threshold: float = 0.8,
                 outlier_detection: bool = True,
                 outlier_method: str = "isolation_forest",
                 contamination: float = 0.1,
                 learning_rate: float = 0.1,
                 adaptation_threshold: float = 0.6):
        """
        初始化实时更新系统
        
        Args:
            max_samples_per_key: 每个键位的最大样本数
            update_threshold: 更新阈值
            outlier_detection: 是否启用离异点检测
            outlier_method: 离异点检测方法
            contamination: 离异点比例
            learning_rate: 学习率
            adaptation_threshold: 适应阈值
        """
        self.max_samples_per_key = max_samples_per_key
        self.update_threshold = update_threshold
        self.outlier_detection = outlier_detection
        self.outlier_method = outlier_method
        self.contamination = contamination
        self.learning_rate = learning_rate
        self.adaptation_threshold = adaptation_threshold
        
        # 样本存储
        self.sample_storage = {}
        self.outlier_detectors = {}
        
        logger.info("实时更新系统初始化完成")
    
    def update_sample(self, 
                     classifier, 
                     feature_vector: np.ndarray, 
                     correct_key: str,
                     predicted_key: Optional[str] = None,
                     confidence: Optional[float] = None):
        """
        更新样本
        
        Args:
            classifier: 分类器对象
            feature_vector: 特征向量
            correct_key: 正确键位
            predicted_key: 预测键位
            confidence: 预测置信度
        """
        try:
            # 检查是否需要更新
            if not self._should_update_sample(classifier, feature_vector, correct_key, confidence):
                return
            
            # 离异点检测
            if self.outlier_detection and self._is_outlier(feature_vector, correct_key):
                logger.info(f"检测到离异点，跳过更新: {correct_key}")
                return
            
            # 添加样本
            self._add_sample(feature_vector, correct_key)
            
            # 更新分类器
            self._update_classifier(classifier)
            
            logger.info(f"样本已更新: {correct_key}")
            
        except Exception as e:
            logger.error(f"样本更新失败: {e}")
    
    def _should_update_sample(self, 
                            classifier, 
                            feature_vector: np.ndarray, 
                            correct_key: str,
                            confidence: Optional[float] = None) -> bool:
        """
        判断是否应该更新样本
        
        Args:
            classifier: 分类器
            feature_vector: 特征向量
            correct_key: 正确键位
            confidence: 预测置信度
            
        Returns:
            是否应该更新
        """
        # 如果置信度低于阈值，需要更新
        if confidence is not None and confidence < self.update_threshold:
            return True
        
        # 如果预测错误，需要更新
        if hasattr(classifier, 'predict'):
            predicted = classifier.predict(feature_vector.reshape(1, -1))[0]
            if predicted != correct_key:
                return True
        
        # 检查样本数量限制
        if correct_key in self.sample_storage:
            if len(self.sample_storage[correct_key]) >= self.max_samples_per_key:
                # 替换最旧的样本
                return True
        
        return True
    
    def _is_outlier(self, feature_vector: np.ndarray, key: str) -> bool:
        """
        检测是否为离异点
        
        Args:
            feature_vector: 特征向量
            key: 键位
            
        Returns:
            是否为离异点
        """
        if key not in self.sample_storage or len(self.sample_storage[key]) < 10:
            return False
        
        # 获取现有样本
        existing_samples = np.array(self.sample_storage[key])
        
        # 初始化离异点检测器
        if key not in self.outlier_detectors:
            if self.outlier_method == "isolation_forest":
                self.outlier_detectors[key] = IsolationForest(
                    contamination=self.contamination,
                    random_state=42
                )
            elif self.outlier_method == "local_outlier_factor":
                self.outlier_detectors[key] = LocalOutlierFactor(
                    contamination=self.contamination,
                    novelty=True
                )
            else:
                return False
        
        # 训练检测器
        try:
            detector = self.outlier_detectors[key]
            if self.outlier_method == "isolation_forest":
                detector.fit(existing_samples)
                prediction = detector.predict(feature_vector.reshape(1, -1))
            else:  # local_outlier_factor
                detector.fit(existing_samples)
                prediction = detector.predict(feature_vector.reshape(1, -1))
            
            # -1表示离异点
            return prediction[0] == -1
            
        except Exception as e:
            logger.warning(f"离异点检测失败: {e}")
            return False
    
    def _add_sample(self, feature_vector: np.ndarray, key: str):
        """
        添加样本
        
        Args:
            feature_vector: 特征向量
            key: 键位
        """
        if key not in self.sample_storage:
            self.sample_storage[key] = []
        
        # 检查样本数量限制
        if len(self.sample_storage[key]) >= self.max_samples_per_key:
            # 移除最旧的样本
            self.sample_storage[key].pop(0)
        
        # 添加新样本
        self.sample_storage[key].append(feature_vector.flatten())
    
    def _update_classifier(self, classifier):
        """
        更新分类器
        
        Args:
            classifier: 分类器对象
        """
        try:
            # 准备训练数据
            X_train = []
            y_train = []
            
            for key, samples in self.sample_storage.items():
                if len(samples) > 0:
                    X_train.extend(samples)
                    y_train.extend([key] * len(samples))
            
            if len(X_train) > 0:
                X_train = np.array(X_train)
                y_train = np.array(y_train)
                
                # 重新训练分类器
                if hasattr(classifier, 'fit'):
                    classifier.fit(X_train, y_train)
                    logger.info(f"分类器已更新，样本数={len(X_train)}")
                
        except Exception as e:
            logger.error(f"分类器更新失败: {e}")
    
    def get_sample_statistics(self) -> Dict[str, Dict]:
        """
        获取样本统计信息
        
        Returns:
            样本统计字典
        """
        stats = {}
        
        for key, samples in self.sample_storage.items():
            if len(samples) > 0:
                samples_array = np.array(samples)
                stats[key] = {
                    'count': len(samples),
                    'mean_features': np.mean(samples_array, axis=0).tolist(),
                    'std_features': np.std(samples_array, axis=0).tolist(),
                    'max_samples': self.max_samples_per_key
                }
            else:
                stats[key] = {
                    'count': 0,
                    'mean_features': [],
                    'std_features': [],
                    'max_samples': self.max_samples_per_key
                }
        
        return stats
    
    def clear_samples(self, key: Optional[str] = None):
        """
        清除样本
        
        Args:
            key: 要清除的键位，如果为None则清除所有
        """
        if key is None:
            self.sample_storage.clear()
            self.outlier_detectors.clear()
            logger.info("所有样本已清除")
        else:
            if key in self.sample_storage:
                del self.sample_storage[key]
            if key in self.outlier_detectors:
                del self.outlier_detectors[key]
            logger.info(f"键位 {key} 的样本已清除")
    
    def get_total_samples(self) -> int:
        """
        获取总样本数
        
        Returns:
            总样本数
        """
        total = 0
        for samples in self.sample_storage.values():
            total += len(samples)
        return total
    
    def get_system_info(self) -> Dict:
        """
        获取系统信息
        
        Returns:
            系统信息字典
        """
        info = {
            'max_samples_per_key': self.max_samples_per_key,
            'update_threshold': self.update_threshold,
            'outlier_detection': self.outlier_detection,
            'outlier_method': self.outlier_method,
            'contamination': self.contamination,
            'learning_rate': self.learning_rate,
            'adaptation_threshold': self.adaptation_threshold,
            'total_samples': self.get_total_samples(),
            'sample_statistics': self.get_sample_statistics()
        }
        
        return info
    
    def __str__(self) -> str:
        total_samples = self.get_total_samples()
        return f"RealTimeUpdateSystem(samples={total_samples}, threshold={self.update_threshold})"
    
    def __repr__(self) -> str:
        return self.__str__() 